package com.rad.net.protocol.android.encoder;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.Set;

import android.text.TextUtils;

import com.rad.net.protocol.android.encoder.ExtraParam.RequestMethod;
import com.rad.net.protocol.android.http.Request;
import com.rad.net.protocol.android.utils.ClassHelper;
import com.rad.net.protocol.android.utils.LogUtils;
import com.rad.net.protocol.common.annotation.GET;
import com.rad.net.protocol.common.annotation.POST;
import com.rad.net.protocol.common.cmd.BaseCmd;


/** 
 * 类说明：   编码器抽象基类，该类通过cmd自动加载字段，拼装参数，生成联网器请求对象
 * @author  xinhui.cheng
 * @date    2014-8-20
 * @version 1.0
 */
public abstract class BaseEncoder implements Encoder{
	
	private static final String TAG = BaseEncoder.class.getSimpleName();
	
	private static final String CHARSET = "UTF-8";
	private static final int LIMIT_BYTE_LENGTH = 50*1024;
	/**
	 * POST BOUNDARY格式的数据分隔符
	 */
	protected static final String BOUNDARY = "----WebKitFormBoundaryMUvOZK7PK8dJE0vy";
	/**
	 * POST BOUNDARY格式的前缀标识字符"--"
	 */
	protected static final String PREFIX = "--";
	/**
	 * POST BOUNDARY格式的结尾标识字符<b>"\r\n"
	 */
	protected final String LINEND = "\r\n";
	/**
	 * 需要过滤的字段
	 */
	private Set<String> mFilterFieldNames;
	
	private BaseCmd mCmd;
	
	private HashMap<String, Object> mGetParams = new HashMap<String, Object>();
	private HashMap<String, Object> mPostParams = new HashMap<String, Object>();
	/**
	 * 二进制和文件参数，例如byte[],file
	 */
	private HashMap<String, Object> mBinParams = new HashMap<String, Object>();
	
	/**
	 * 构造对象时会初始化过滤字段集合
	 */
	public BaseEncoder(){
		mFilterFieldNames = new HashSet<String>();
	}
	
	/**
	 * 获取需要添加的额外参数链表
	 * @param basicParams 经过分析生成的基本参数，Object可能为{@link List}或普通对象，
	 *                但<b>不包含byte[],File</b>类型.
	 * @return 额外参数对象链表
	 */
	protected abstract List<ExtraParam> getExtraParams(TreeMap<String, Object> basicParams);
	
	/**
	 * 添加需要过滤的字段名，添加的字段名在后续分析加载字段的时候就会被过滤掉
	 * @see {@link ClassHelper#loadClassFields(Class, Set)
	 * @param fieldName 字段名称
	 */
	public void addFilterFieldName(String fieldName){
		mFilterFieldNames.add(fieldName);
	}
	
	/**
	 * 获取BaseCmd
	 * @return cmd对象
	 */
	public BaseCmd getBaseCmd(){
		return mCmd;
	}
	
	@Override
	public Request encode(BaseCmd cmd) {
		if(cmd == null){
			return null;
		}
		mCmd = cmd;
		ArrayList<FieldInfo> fieldInfos = ClassHelper.loadClassFields(mCmd.getClass(),mFilterFieldNames);
		//校验参数
		String errorInfo = EncodeValidator.verifyClassFields(mCmd, fieldInfos);
		if(!TextUtils.isEmpty(errorInfo)){
			throw new IllegalArgumentException(errorInfo);
		}
		buildParams(fieldInfos);
		TreeMap<String, Object> basicParams = new TreeMap<String, Object>();
		basicParams.putAll(mGetParams);
		basicParams.putAll(mPostParams);
		buildExtraParams(basicParams);
		Request request = params2Request();
		//清除参数，以便下次使用
		clearParams();
		
		return request;
	}
	
	private void buildExtraParams(TreeMap<String, Object> basicParams){
		List<ExtraParam> params = getExtraParams(basicParams);
		if(params == null || params.size() == 0){
			return;
		}
		for(ExtraParam param : params){
			analyzeExtraParam(param);
		}
	}
	
	private void analyzeExtraParam(ExtraParam param){
		RequestMethod method = param.getMethod();
		Object value = param.getValue();
		String key = param.getKey();
		Class<?> type = param.getValueClass();
		if(param == null || TextUtils.isEmpty(key) || method == null || type == null){
			throw new NullPointerException("ExtraParam or key or method or valueType is null!");
		}
		if(method == RequestMethod.POST){
			analyzePostField(key, value, type);
		} else {
			analyzeGetField(key, value, type);
		}
	}
	
	private Request params2Request(){
		Request request = new Request();
		request.setUrl(buildRequestUrl());
		request.setHeader(getRequestHeader());
		request.setBody(buildHttpBody());
		return request;
	}
	
	private String buildRequestUrl(){
		StringBuilder url = new StringBuilder();
		url.append(mCmd.getServerUrl());
		if(mGetParams.size() > 0){
			if(mCmd.getServerUrl().indexOf("?") < 0){
				url.append("?");
			}
			url.append(packageNormalParams(mGetParams));
		}
		LogUtils.i(TAG, "request url:"+url.toString());
		return url.toString();
	}
	
	/**
	 * 以键值对形式拼装普通参数
	 * @param params
	 * @return 结果字符串
	 */
	@SuppressWarnings("unchecked")
	private String packageNormalParams(Map<String, Object> params){
		StringBuilder data = new StringBuilder();
		for(Entry<String, Object> entry: params.entrySet()){
			Object value = entry.getValue();
			String key = entry.getKey();
			try {
				if(value instanceof List){
					List<Object> list = (List<Object>) value;
					for(Object o : list){
						if(o != null){
							data.append(key+"="+URLEncoder.encode(String.valueOf(o), CHARSET)+"&");
						} else {
							data.append(key+"=&");
						}
					}
				} else {
					if(value != null){
						data.append(key+"="+URLEncoder.encode(String.valueOf(value), CHARSET)+"&");
					} else {
						data.append(key+"=&");
					}
				}
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}
		if(data.length() > 0){
			data.deleteCharAt(data.length()-1);
		}
		return data.toString();
	}
	
	/**
	 * 构建POST请求体数据，即POST的实体
	 * @return 输入流或者null
	 */
	protected InputStream buildHttpBody(){
		if(mBinParams.size() > 0){
			return buildPostBoundaryData();
		} else {
			if(mPostParams.size() > 0){
				return buildPostData();
			}
		}
		return null;
	}
	
	private InputStream buildPostData(){
		String postData = packageNormalParams(mPostParams);
		LogUtils.i(TAG, "post params:"+postData);
		return new ByteArrayInputStream(postData.getBytes());
	}
	
	@SuppressWarnings("unchecked")
	private InputStream buildPostBoundaryData(){
		StringBuilder sb = new StringBuilder();
		ArrayList<InputStream> inputStreams = new ArrayList<InputStream>();
		for(Entry<String, Object> entry: mPostParams.entrySet()){
			Object value = entry.getValue();
			String key = entry.getKey();
			try{
				if(value instanceof List){
					List<Object> list = (List<Object>) value;
					for(Object o : list){
						appendPostBoundaryParam(sb, key, URLEncoder.encode(String.valueOf(o), CHARSET));
					}
				} else {
					appendPostBoundaryParam(sb, key, URLEncoder.encode(String.valueOf(value), CHARSET));
				}
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}
		if(sb.length() > 0){
			inputStreams.add(new ByteArrayInputStream(sb.toString().getBytes()));
		}
		for(Entry<String, Object> entry: mBinParams.entrySet()){
			Object value = entry.getValue();
			String key = entry.getKey();
			if(value instanceof List){
				List<File> files = (List<File>) value;
				for(File f : files){
					inputStreams.add(new ByteArrayInputStream(
							getBoundaryStreamStart(key,f.getName()).toString().getBytes()));
					try {
						inputStreams.add(new FileInputStream(f));
					} catch (FileNotFoundException e) {
						throw new IllegalArgumentException(e.getMessage());
					}
					inputStreams.add(new ByteArrayInputStream(LINEND.getBytes()));
				}
			} else if(value instanceof byte[]){
				inputStreams.add(new ByteArrayInputStream(
						getBoundaryStreamStart(key,null).toString().getBytes()));
				inputStreams.add(new ByteArrayInputStream((byte[])value));
				inputStreams.add(new ByteArrayInputStream(LINEND.getBytes()));
			} else if(value instanceof File){
				File file = (File) value;
				inputStreams.add(new ByteArrayInputStream(
						getBoundaryStreamStart(key,file.getName()).toString().getBytes()));
				try {
					inputStreams.add(new FileInputStream(file));
				} catch (FileNotFoundException e) {
					throw new IllegalArgumentException(e.getMessage());
				}
				inputStreams.add(new ByteArrayInputStream(LINEND.getBytes()));
			}
		}
		if(inputStreams.size() > 0){
			inputStreams.add(new ByteArrayInputStream(getBoundaryEnd().getBytes()));
			MultipleInputStream is = new MultipleInputStream(inputStreams);
			return is;
		}
		return null;
	}
	
	/**
	 * 添加POST BOUNDARY参数，将键值根据规定格式添加到字符串中
	 * @param sb 键值对会被格式化添加到这个动态字符串中
	 * @param key 需要添加的键
	 * @param value 需要添加的值
	 */
	protected void appendPostBoundaryParam(StringBuilder sb, String key, String value) {
		sb.append(PREFIX + BOUNDARY + LINEND
						+ "Content-Disposition: form-data; name=\"" + key
						+ "\"" + LINEND)
		  .append(LINEND)
		  .append(value==null?"":value)
		  .append(LINEND);
	}
	
	/**
	 * 获取POST BOUNDARY文件或二进制起始字符串
	 * @param key  键
	 * @param fileName 文件名
	 * @return 结果字符串
	 */
	protected String getBoundaryStreamStart(String key, String fileName){
		StringBuilder sb = new StringBuilder();
		sb.append(PREFIX + BOUNDARY + LINEND + "Content-Disposition: form-data;" + " name=\"" + key + "\"");
		if(!TextUtils.isEmpty(fileName)){
			sb.append(";filename=\""+fileName+"\"");
		}
		sb.append(LINEND + "Content-Type:application/octet-stream" + LINEND + LINEND);
		return sb.toString();
	}
	
	/**
	 * 获取POST BOUNDARY的结尾字符串
	 * @return {@link #LINEND}， {@link #PREFIX}， {@link #BOUNDARY}字符串的组合
	 */
	protected String getBoundaryEnd(){
		return LINEND + PREFIX + BOUNDARY + PREFIX + LINEND;
	}
	
	/**
	 * 构建参数
	 * @param fieldInfos 属性值域对象
	 */
	private void buildParams(ArrayList<FieldInfo> fieldInfos){
		for (int i = 0; i < fieldInfos.size(); i++) {
			FieldInfo info = fieldInfos.get(i);
			analyzeField(info);
		}
	}

	/**
	 * 分析类字段信息
	 * @param info 属性值域对象
	 */
	private void analyzeField(FieldInfo info){
		POST classPost = mCmd.getClass().getAnnotation(POST.class);
		if(classPost != null){
			GET get = info.getField().getAnnotation(GET.class);
			if(get != null){
				analyzeGetField(info);
			} else {
				analyzePostField(info);
			}
		} else {
			POST post = info.getField().getAnnotation(POST.class);
			if(post != null){
				analyzePostField(info);
			} else {
				analyzeGetField(info);
			}
		}
		
	}
	/**
	 * 分析POST参数
	 * @param info 属性值域对象
	 */
	private void analyzePostField(FieldInfo info){
		Object value = info.getAttribute(mCmd);
		String key = info.getAlias();
		analyzePostField(key, value, info.getField().getType());
	}
	
	private void analyzePostField(String key,Object value, Class<?> valueType){
		if(value instanceof File[]){
			File[] files = (File[]) value;
			mBinParams.put(key, Arrays.asList(files));
			return ;
		}
		if(value instanceof File){
			mBinParams.put(key, value);
			return ;
		}
		if(value instanceof byte[]){
			byte[] bs = (byte[]) value;
			if(bs.length > LIMIT_BYTE_LENGTH){
				throw new IllegalStateException("byte[] size must be smaller than "+LIMIT_BYTE_LENGTH);
			}
			mBinParams.put(key, value);
			return ;
		}
		if(value == null){
			if(valueType == File.class || valueType == File[].class || valueType == byte[].class){
				mBinParams.put(key, value);
			}
		}
		if (valueType.isArray()) {
			mPostParams.put(key, arrayObj2List(value));
			return;
		} else {
			mPostParams.put(key, value);
			return;
		}
	}
	
	/**
	 * 分析Get参数
	 * @param info 属性值域对象
	 */
	private void analyzeGetField(FieldInfo info){
		Object value = info.getAttribute(mCmd);
		String key = info.getAlias();
		analyzeGetField(key, value, info.getField().getType());
	}
	
	private void analyzeGetField(String key,Object value, Class<?> valueType){
		if(value instanceof File[]){
			throw new IllegalStateException("File[] type can't use GET method");
		}
		if(value instanceof File){
			throw new IllegalStateException("File type can't use GET method");
		}
		if(value instanceof byte[]){
			throw new IllegalStateException("byte[] type can't use GET method");
		}
		if(value == null){
			if(valueType == File.class || valueType == File[].class || valueType == byte[].class){
				throw new IllegalStateException("byte[],File,File[] type can't use GET method");
			}
		}
		if(valueType.isArray()){
			mGetParams.put(key, arrayObj2List(value));
			return ;
		} else {
			mGetParams.put(key, value);
			return ;
		}
	}
	
	private ArrayList<Object> arrayObj2List(Object value){
		if(value == null){
			return null;
		}
		ArrayList<Object> list = new ArrayList<Object>();
		int length = Array.getLength(value);
		for (int i = 0; i < length; i++) {
			list.add(Array.get(value, i));
		}
		return list;
	}
	
	/**
	 * 清除所有请求参数
	 */
	public void clearParams(){
		mGetParams.clear();
		mPostParams.clear();
		mBinParams.clear();
	}
	
	private HashMap<String, String> getRequestHeader(){
		if(mBinParams.size() != 0){
			return getPostBoundaryRequestHeader();
		} else if(mPostParams.size() != 0){
			return getPostRequestHeader();
		} else {
			return getGetRequestHeader();
		}
	}
	/**
	 * 默认的header参数
	 * @return 键值对格式的集合
	 */
	protected HashMap<String, String> getFixedHeader(){
		HashMap<String, String> map = new HashMap<String, String>();
		map.put("Accept", "*/*");
		map.put("Accept-Language", "zh-CN, zh");
		map.put("Charset",
				"UTF-8,ISO-8859-1,US-ASCII,ISO-10646-UCS-2;q=0.6");
		map.put("User-Agent",
				"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0;" +
				" .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152;" +
				" .NET CLR 3.5.30729)");
		map.put("Connection", "Keep-Alive");
		map.put("Accept-Encoding", "gzip, deflate");
		return map;
	}
	/**
	 * 以GET方式请求的header参数,子类可重写该方法以替换成自定义的header;
	 * @return 键值对格式的集合
	 */
	protected HashMap<String,String> getGetRequestHeader(){
		return getFixedHeader();
	}
	/**
	 * 以POST方式请求的header参数,子类可重写该方法以替换成自定义的header;
	 * @return 键值对格式的集合
	 */
	protected HashMap<String, String> getPostRequestHeader(){
		HashMap<String, String> map = new HashMap<String, String>();
		map.putAll(getFixedHeader());
		map.put("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
		return map;
	}
	/**
	 * 以POST BOUNDARY方式请求的header参数,子类可重写该方法以替换成自定义的header;
	 * @return 键值对格式的集合
	 */
	protected HashMap<String, String> getPostBoundaryRequestHeader(){
		HashMap<String, String> map = new HashMap<String, String>();
		map.put("connection", "keep-alive");
		map.put("Content-Type",	"multipart/form-data;boundary=" + BOUNDARY);
		map.put("Accept-Charset","GBK,utf-8;q=0.7,*;q=0.3");
		map.put("Accept-Encoding","gzip,deflate,sdch");
		map.put("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
		map.put("User-Agent","Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 ");
		return map;
	}
	
	/**
	 * 字符串以md5方式加密，默认的字符编码格式是"UTF-8"
	 * @param str 需要加密的字符串
	 * @return 加密后的字符串
	 */
	protected String md5(String str) {
		if (str == null) {
			return null;
		}
		return md5(str, CHARSET);
	}

	private String md5(String str, String encodingType) {
		MessageDigest md5 = null;
		try {
			md5 = MessageDigest.getInstance("MD5");
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}

		try {
			md5.update(str.getBytes(encodingType));
		} catch (UnsupportedEncodingException e) {
			md5.update(str.getBytes());
		}

		byte[] md5Bytes = md5.digest();

		StringBuffer hexValue = new StringBuffer();
		for (int i = 0; i < md5Bytes.length; i++) {
			int val = ((int) md5Bytes[i]) & 0xff;
			if (val < 16) {
				hexValue.append("0");
			}
			hexValue.append(Integer.toHexString(val));
		}
		return hexValue.toString();
	}
	

}
